package org.obm.lmtp.impl;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;

import org.apache.mina.core.session.IoSession;
import org.obm.lmtp.backend.LmtpEnvelope;
import org.obm.mina.BlobBuilder;
import org.obm.mina.LineBuffer;
import org.obm.mina.MinaHandler;
import org.obm.mina.MinaIoSessionOutputStream;
import org.obm.mina.MinaOutputStream;
import org.obm.mina.ServiceException;
import org.obm.mina.StoreManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MinaLmtpHandler extends LmtpHandler implements MinaHandler {

	private static final Logger logger = LoggerFactory
			.getLogger(MinaLmtpHandler.class);

	private final IoSession mSession;
	private BlobBuilder data;
	private MinaOutputStream mOutputStream;

	private static final long WRITE_TIMEOUT = 5000;

	public MinaLmtpHandler(MinaLmtpServer server, IoSession session) {
		super(server);
		mSession = session;
		mEnvelope = new LmtpEnvelope();
		mOutputStream = new MinaIoSessionOutputStream(mSession);
		writer = new LmtpWriter(mOutputStream);
	}

	public void messageReceived(Object msg) throws IOException {
		if (data != null) {
			receiveData((LineBuffer) msg);
			if (data.isFinished()) {
				processMessageData(data.getBlob());
				data.dispose();
				data = null;
			}
		} else {
			processCommand((String) msg);
		}
	}

	private void receiveData(LineBuffer lb) throws IOException {
		if (lb.matches(".\r\n")) {
			try {
				data.finish();
			} catch (ServiceException e) {
				throw (IOException) new IOException("Unable to write blob")
						.initCause(e);
			}
		} else {
			ByteBuffer bb = lb.buf();
			if (lb.startsWith("..")) {
				bb.position(bb.position() + 1); // Skip leading '.'
			}
			data.append(bb);
		}
	}

	public void connectionOpened() {
		setupConnection(((InetSocketAddress) mSession.getRemoteAddress())
				.getAddress());
	}

	public void connectionClosed() {
		cleanup();
	}

	public void connectionIdle() {
		notifyIdleConnection();
		dropConnection();
	}

	@Override
	protected void continueDATA() throws IOException {
		try {
			data = StoreManager.getInstance().getBlobBuilder();
		} catch (ServiceException e) {
			throw (IOException) new IOException("Unable to create blob builder")
					.initCause(e);
		}
		data.append(getAdditionalHeaders().getBytes());
	}

	@Override
	protected void dropConnection() {
		dropConnection(WRITE_TIMEOUT);
	}

	public void dropConnection(long timeout) {
		if (!mSession.isConnected())
			return;
		try {
			mOutputStream.close();
		} catch (IOException e) {
			// Should never happen...
			logger.error("error closing stream", e);
		}
		mOutputStream.flushAndClose();
		if (data != null) {
			data.dispose();
			data = null;
		}
	}

	private void cleanup() {
		if (mSession.isConnected()) {
			mSession.close(true);
		}
		if (data != null) {
			data.dispose();
			data = null;
		}
	}

	@Override
	protected boolean setupConnection(Socket connection) {
		throw new UnsupportedOperationException();
	}

	@Override
	protected boolean processCommand() {
		throw new UnsupportedOperationException();
	}
}
